﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using System.Data.Entity.Core.Metadata.Edm;
using System.Reflection;

using Seven.EntityBasic;
using Seven.MsSql.Annotations;
using Seven.Tools.Extension;

namespace Seven.MsSql.Context
{
    /// <summary>
    /// 提供一组对 基础对象类型 <see cref="System.Object"/> 操作方法的扩展。
    /// 主要用于基于实体对象的操作进行扩展。
    /// </summary>
    public static class EntityObjectExtensions
    {
        private static Dictionary<Type, Delegate> copyCache = new Dictionary<Type, Delegate>();
        
        /// <summary>
        /// 基于指定的实体数据库上下文创建一个当前对象的新副本，该副本和当前对象为同一类型，且其中各个属性的值均等同于原对象中各个属性的值，相当于浅表复制操作。
        /// 但是该操作在复制数据的过程中将不会复制实体对象的导航（外键引用等）属性。
        /// </summary>
        /// <typeparam name="TEntity">要执行复制操作的对象的类型。</typeparam>
        /// <param name="_this">要执行复制操作的对象。</param>
        /// <param name="context">实体数据库上下文对象。</param>
        /// <returns>返回一个当前对象的副本，返回的对象中各个属性值均相当于源对象 <paramref name="_this"/> 的各个属性值，但导航（外键引用等）属性除外。</returns>
        public static TEntity DuplicateWithNonNavigationProperties<TEntity, TKey>(this TEntity _this, System.Data.Entity.DbContext context)
            where TKey : struct
            where TEntity : EntityBase<TKey>, new()
        {
            if (_this == null)
            { return null; }

            Type thisType = typeof(TEntity);
            if (thisType.IsAbstract)
                thisType = _this.GetType();

            EntityTable table = context.GetEntityTable(thisType);
            if (table == null || table.ModelType == null)
            { return _this; }

            NavigationProperty[] nps = table.ModelType.DeclaredNavigationProperties.ToArray();
            if (nps.Length == 0)
            { return _this; }

            var properties = (from p in thisType.GetProperties()
                              where p.GetMethod != null && p.SetMethod != null && !p.PropertyType.IsValueType && nps.Any(np => np.Name == p.Name)
                              select p).ToArray();

            if (properties.Length == 0)
            { return _this; }

            TEntity obj = _this.Duplicate();
            foreach (PropertyInfo p in properties)
            {
                p.SetValue(obj, null);
            }

            return obj;
        }

        /// <summary>
        /// 创建一个当前对象的新副本，该副本和当前对象为同一类型，且其中各个属性的值均等同于原对象中各个属性的值，相当于浅表复制操作。
        /// </summary>
        /// <typeparam name="TEntity">要执行复制操作的对象的类型。</typeparam>
        /// <typeparam name="Tkey">要执行复制操作的对象的类型的主键类型</typeparam>
        /// <param name="_this">要执行复制操作的对象。</param>
        /// <returns>返回一个当前对象的副本，返回的对象中各个属性值均相当于源对象 <paramref name="_this"/> 的各个属性值</returns>
        public static TEntity CopyByDelegate<TEntity, Tkey>(this TEntity _this)
            where Tkey : struct
            where TEntity : EntityBase<Tkey>
        {
            if (_this == null) { return _this; }
            Type entityType = typeof(TEntity);
            Delegate del;
            if (!copyCache.TryGetValue(entityType, out del))
            {
                del = CreateCopyDelegate<TEntity>();
                Seven.Tools.Utility.TryCatchExecute(() => copyCache.Add(entityType, del));
            }
            return ((Func<TEntity, TEntity>)del)(_this);
        }

        /// <summary>
        /// 创建复制委托
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <returns></returns>
        private static Func<TEntity, TEntity> CreateCopyDelegate<TEntity>()
        {
            Type entityType = typeof(TEntity);
            //lambda的方法定义中的参数
            var param = Expression.Parameter(entityType, "e");
            // Lambda方法体中的变量集
            var methodVars = new List<ParameterExpression>();
            // Lambda方法体中的语句集
            var methodBodies = new List<Expression>();
            methodBodies.Add(FieldAssign(entityType, param, methodBodies, methodVars));
            // 将语句集组合成一个语句块
            var MethodBlock = Expression.Block(entityType, methodVars, methodBodies);
            // 将表达式树生成委托
            return Expression.Lambda<Func<TEntity, TEntity>>(MethodBlock, param).Compile();
        }

        private static Expression FieldAssign(Type entityType, Expression sourceExp, List<Expression> bodies, List<ParameterExpression> paramList)
        {
            /*
             * e =>
             * {
             *      var a;
             *      a = new SysUser();
             *      a.id = e.id;
             *      a.name = e.name
             *      a.Right = e.Right
             //*      if (e.Right != null)
             //*      {
             //*          var b ;
             //*          b = new SysRight();
             //*          b.xx = e.Right.xx
             //*          a.right = b;
             //*      }
             *      return a;
             * }
             */
            var newEntityVar = Expression.Variable(entityType);
            paramList.Add(newEntityVar);//var a;
            bodies.Add(Expression.Assign(newEntityVar, Expression.New(entityType)));// a = new T();

            var properties = entityType.GetProperties().Where(w => w.GetMethod != null && w.SetMethod != null);
            foreach (var prop in properties)
            {
                bodies.Add(Expression.Assign(Expression.Property(newEntityVar, prop), Expression.Property(sourceExp, prop)));// a.X = e.X
            }

            //var fields = entityType.GetFields();
            //foreach (var field in fields)
            //{
            //    //// 值类型处理
            //    //if (field.FieldType.IsValueType || field.FieldType == typeof(string))
            //    //{
            //    //    bodies.Add(Expression.Assign(Expression.Field(newEntityVar, field), Expression.Field(sourceExp, field)));
            //    //}
            //    //// 类的深度处理
            //    //else if (field.FieldType.IsClass && !field.FieldType.IsGenericType)
            //    //{
            //    //    var nullValue = Expression.Constant(null, field.FieldType);
            //    //    var a = Expression.NotEqual(Expression.Field(sourceExp, field), nullValue);
            //    //    List<ParameterExpression> ifThenParams = new List<ParameterExpression>();
            //    //    List<Expression> ifThenBodies = new List<Expression>();
            //    //    var b = Expression.Assign(Expression.Field(newEntityVar, field), FieldAssign(field.FieldType, Expression.Field(sourceExp, field), ifThenBodies, ifThenParams));
            //    //    ifThenBodies.Add(b);
            //    //    var c = Expression.Block(ifThenParams, ifThenBodies);
            //    //    bodies.Add(Expression.IfThen(a, c));
            //    //}
            //    bodies.Add(Expression.Assign(Expression.Field(newEntityVar, field), Expression.Field(sourceExp, field)));
            //}
            return newEntityVar;
        }
    }
}
